// map handling routines

function MapProperties(AParentNode) {
  // parse root node attributes
  this.version = AParentNode.getAttribute('version');
  this.orientation = AParentNode.getAttribute('orientation');
  this.width = AParentNode.getAttribute('width');
  this.height = AParentNode.getAttribute('height');
  this.tilewidth = AParentNode.getAttribute('tilewidth');
  this.tileheight = AParentNode.getAttribute('tileheight');
  // parse map properties node
  for (var c=0; c<AParentNode.childNodes.length; c++) {
    if (AParentNode.childNodes[c].nodeName == 'properties') {
      properties = AParentNode.childNodes[c];
      var property = properties.getElementsByTagName('property');
      for (var i=0; i<property.length; i++) {
        var n = property[i].getAttribute('name');
        var v = property[i].getAttribute('value');
        if (n == 'name')             this.name = v;
        if (n == 'music')            this.music = v;
        if (n == 'minimap')          this.minimap = v;
        if (n == 'overlay0image')    this.overlay0image = v;
        if (n == 'overlay0parallax') this.overlay0parallax = v;
        if (n == 'overlay0scrollX')  this.overlay0scrollX = v;
        if (n == 'overlay0scrollY')  this.overlay0scrollY = v;
      }
    }
  }
  //debug('MapProperties:\n  n='+this.name+'\n  m='+this.music+'\n  mm='+this.minimap+'\n  im='+this.overlay0image+'\n  p='+this.overlay0parallax+'  s='+this.overlay0scrollX+','+this.overlay0scrollY);
}

function MapTileset(AParentNode) {
  // parse tilesets
  var that = new Array();
  var tileset = AParentNode.getElementsByTagName('tileset');
  //debug('MapTileset: length='+tileset.length);
  for (var t=0; t<tileset.length; t++) {
    that[t] = new Object;
    that[t].firstgid = 1*tileset[t].getAttribute('firstgid');
    that[t].name = tileset[t].getAttribute('name');
    that[t].tilewidth = 1*tileset[t].getAttribute('tilewidth');
    that[t].tileheight = 1*tileset[t].getAttribute('tileheight');
    var image = tileset[t].getElementsByTagName('image');
    if (image.length != 1)
      throw "MapTileset: image length is "+image.length;
    that[t].source = image[0].getAttribute('source').replace('../','');
    // load image
    that[t].image = new Image();
    that[t].image.tilewidth = that[t].tilewidth;
    that[t].image.tileheight = that[t].tileheight;
    that[t].image.onload = function() {
      this.modx = Math.floor(this.width / this.tilewidth);
      this.mody = Math.floor(this.height / this.tileheight);
      //debug('  mod='+this.modx+','+this.mody+' src='+this.src);
    }
    that[t].image.src = that[t].source;
    //debug('  ['+t+']: fg='+that[t].firstgid+' n='+that[t].name+' tw='+that[t].tilewidth+' th='+that[t].tileheight+' s='+that[t].source.replace('graphics/tiles',''));
  }

  that.convert = function(AIndex) {
    // find which tileset contain this function
    //debug('MapTileset.convert('+AIndex+'):');
    var ti = -1;
    var ind = -1;
    if (AIndex == 0) {
      global_ti = ti;
      global_ind = ind;
      return;
    }
    for (var i=0; i<this.length; i++) {
      //debug('  AIndex='+AIndex+' fg='+this[i].firstgid);
      if (AIndex >= this[i].firstgid) {
        // last
        if (i == this.length-1) {
          ti = i;
          break;
        }
        // is it lower than next
        if (AIndex < this[i+1].firstgid) {
          ti = i;
          break;
        }
      }
    }
    //debug('  ti='+ti);
    // calculate read index
    ind = AIndex - this[ti].firstgid;
    //debug('  ind='+ind);
    global_ti = ti;
    global_ind = ind;
  }
  
  return that;
}

function MapLayer(AParentNode) {
  // parse map layer
  /*
  <layer name="Collision" width="117" height="75" visible="0">
    <data encoding="csv">
      10,5,3,
      11,7,8,
      3,4,5
    </data>
  </layer>
  */
  var that = new Array();
  var layer = AParentNode.getElementsByTagName('layer');
  //debug('MapLayer: length='+layer.length);
  for (var i=0; i<layer.length; i++) {
    that[i] = new Object;
    that[i].name = layer[i].getAttribute('name');
    that[i].width = layer[i].getAttribute('width');
    that[i].height = layer[i].getAttribute('height');
    that[i].visible = true;
    if (1*layer[i].getAttribute('visible') == 0)
      that[i].visible = false;
    if (layer[i].getElementsByTagName('data')[0].getAttribute('encoding') != 'csv')
      throw "MapLayer: unsupported encoding '"+layer[i].getElementsByTagName('data')[0].getAttribute('encoding')+"'";
    if (layer[i].getElementsByTagName('data')[0].childNodes.length != 1)
      throw "MapLayer: data childNodes has no exactly 1 item (Firefox?, works fine in Chrome and Opera)"; 
    var d = layer[i].getElementsByTagName('data')[0].childNodes[0].nodeValue.trim().split('\n');
    //debug('d has '+d.length+' lines');
    that[i].data = new Array();
    for (var y=0; y<d.length; y++) {
      var s = d[y].trim();
      if (s[s.length-1]==',')
        s = s.substr(0,s.length-1);
      //debug('data line ['+y+'] = '+s);
      that[i].data[y] = s.split(',');
    }
    //debug(' ['+i+']: n='+that[i].name+' w='+that[i].width+' h='+that[i].height+' v='+that[i].visible+'\nd[0,0]='+that[i].data[0][0]);
  }

  that.byName = function(ALayerName) {
    // return layer index by layer name
    for (var i=0; i<this.length; i++)
      if (this[i].name.toLowerCase() == ALayerName.toLowerCase()) 
        return i;
    return -1;
  }

  return that;
}

function MapObjectProperty(AObjectNode,APropertyName) {
  // object has <property name="DEST_MAP" value="008-1"/> return that value
  var p = AObjectNode.getElementsByTagName('property');
  for (var i=0; i<p.length; i++)
    if (p[i].getAttribute('name').toLowerCase() == APropertyName.toLowerCase())
      return p[i].getAttribute('value');
  return null;
}

function MapWarp(AParentNode) {
  // parse objects of type "warp" and create lookup table for warp points 
  var that = new Array();
  var obj = AParentNode.getElementsByTagName('object');
  var oo = 0;
  for (var i=0; i<obj.length; i++) {
    if (!obj[i].hasAttribute('type'))
      continue;
    if (obj[i].getAttribute('type').toLowerCase() == 'warp') {
      // warp point attributes
      var x = 1*obj[i].getAttribute('x');
      var y = 1*obj[i].getAttribute('y');
      var width = 1*obj[i].getAttribute('width');
      var height = 1*obj[i].getAttribute('height');
      var map = MapObjectProperty(obj[i],'DEST_MAP');
      var mapX = 1*MapObjectProperty(obj[i],'DEST_X');
      var mapY = 1*MapObjectProperty(obj[i],'DEST_Y');
      // some warp point are more than 1 field
      for (var xx=0; xx<width/32; xx++)
        for (var yy=0; yy<height/32; yy++) {
          // lookup table, screw it, im using x+","+y index instead of read 2d array, it would be mostly empty anyway
          var nx = x+xx*32-16;
          var ny = y+yy*32-32;
          var index = nx+','+ny;
          that[index] = new Object();
          that[index].x = nx;
          that[index].y = ny;
          that[index].map = map;
          that[index].mapX = mapX;
          that[index].mapY = mapY;
          ///debug('warp: index='+index+' x='+that[index].x+' y='+that[index].y+' map='+that[index].map+' mapX='+that[index].mapX+' mapY='+that[index].mapY);
        }
    }
  }
  return that;  
}

function Map(AXmlFile) {
  // parse map
  xml = sjax(AXmlFile);
  xml.documentElement.normalize();
  this.properties = new MapProperties(xml.documentElement);
  this.tileset = new MapTileset(xml.documentElement);
  this.layer = new MapLayer(xml.documentElement);
  this.warp = new MapWarp(xml.documentElement);
  
  // detect layer index (not all maps has same amount and order of map layers)
  this.ground = this.layer.byName('Ground');
  this.fringe = this.layer.byName('Fringe');
  this.top = this.layer.byName('Top');
  this.over = this.layer.byName('Over');
  this.collision = this.layer.byName('Collision');
  ///debug('map layers: g='+this.ground+' f='+this.fringe+' t='+this.top+' o='+this.over+' c='+this.collision)

  this.x0 = 0;
  this.y0 = 0;
  
  this.cache = function() {
    // cache while map (original format is slightly hard to read real-time)
    // first reset the map canvas
    ///debug('Caching map '+this.properties.width+'x'+this.properties.height+' with '+this.layer.length+' layers:');
    var st = new Stampt();
    var oi = 0;
    var ooi = 0;
    this.canvas = new Array(this.properties.height);
    for (var y=0; y<this.properties.height; y++) {
      this.canvas[y] = new Array(this.properties.width);
      for (var x=0; x<this.properties.width; x++) {
        this.canvas[y][x] = new Array(this.layer.length);
        // all layers
        for (var l=0; l<this.layer.length; l++) {
          this.canvas[y][x][l] = new Object();
          if (this.layer[l].data[y][x]===undefined)
            debug('  layer['+l+'].data['+y+']['+x+'] is undefined, oi='+oi+' ooi='+ooi);
          ooi = oi;
          oi = this.layer[l].data[y][x];
          this.tileset.convert(this.layer[l].data[y][x]);
          this.canvas[y][x][l].index = global_ind;
          this.canvas[y][x][l].tileset = global_ti;
        }
      }
    }
    ///debug('  cached in '+st.ms());
    //debug('  ground[?][?][0]: index='+this.canvas[15][15][0].index+' tileset='+this.canvas[15][15][0].tileset);
    //debug('  ground[?][?][1]: index='+this.canvas[15][15][1].index+' tileset='+this.canvas[15][15][1].tileset);
    //debug('  ground[?][?][2]: index='+this.canvas[15][15][2].index+' tileset='+this.canvas[15][15][2].tileset);
  }
  this.cache();
  //debug('Map: '+this.properties.name);
  
  this.pan = function() {
    // manual map pan (for testing purposes!)
    if (key[100]) this.x0--;
    if (key[102]) this.x0++;
    if (key[104]) this.y0--;
    if (key[98]) this.y0++;
    if (map.x0 < 0) this.x0 = 0;
    if (map.y0 < 0) this.y0 = 0;
  }
  
  this.draw = function(AOriginX,AOriginY,AWidth,AHeight,AFromLayer,AToLayer) {
    // draw map
    var st = new Stampt();
    var index = 0;
    var tileset = 0;
    var ox = 0;
    var oy = 0;
    var rx = 0;
    var ry = 0;
    var sx = 0;
    var sy = 0;
    if (AFromLayer < 0)
      AFromLayer = 0;
    if ( (AToLayer < AFromLayer)||(AToLayer >= this.layer.length) )
      AToLayer = this.layer.length;
    for (var y=AOriginY; y<(AOriginY+AHeight); y++) {
      for (var l=AFromLayer; l<AToLayer+1; l++) {
        // drawing avatars behind fringe layer
        if ( (l == this.fringe)&&(avatars.length > 0) )
          for (var a=0; a<avatars.length; a++)
            if (y-AOriginY+this.y0 == avatars[a].fringeHook+1)
              avatars[a].draw(avatars[a].x-this.x0*32-vx,avatars[a].y-this.y0*32-vy);
        // map itself              
        for (var x=AOriginX; x<(AOriginX+AWidth+1); x++) {
          // rendering range overflow
          if (y >= this.canvas.length)
            continue;
          if (x >= this.canvas[0].length)
            continue;
          // map itself
          if (!this.canvas[y][x][l])
            continue; // NOTE: some weird bug in archer shop
          index = this.canvas[y][x][l].index;
          tileset = this.canvas[y][x][l].tileset;
          if ((index >= 0)&&(map.tileset[tileset].image.modx>0)) {
            // calculate tile position (depends on tile height!)
            //debug('index='+index+' tileset='+tileset);
            oy = Math.floor(index / map.tileset[tileset].image.modx);
            ox = index - oy*map.tileset[tileset].image.modx;
            // calculate real pixel position
            ry = (oy+0)*map.tileset[tileset].tileheight;
            rx = (ox+0)*map.tileset[tileset].tilewidth;
            // calculate shift for higher tiles than usual 32x32
            sx = (x-AOriginX)*map.tileset[tileset].tilewidth-vx;
            sy = (y-AOriginY)*32-map.tileset[tileset].tileheight+32-vy;
            // debug('ox='+ox+' oy='+oy+' rx='+rx+' ry='+ry+' w='+map.tileset[tileset].tilewidth);
            // draw tile
            context.drawImage(
              map.tileset[tileset].image,
              rx,ry,
              map.tileset[tileset].tilewidth,
              map.tileset[tileset].tileheight,
              sx,sy,
              map.tileset[tileset].tilewidth,
              map.tileset[tileset].tileheight
            );
          }
        }
        // drawing avatars on top of fringe layer
        if ( (l == this.fringe)&&(avatars.length > 0) )
          for (var a=0; a<avatars.length; a++)
            if (y-AOriginY+this.y0 == avatars[a].fringeHook+2)
              avatars[a].draw(avatars[a].x-this.x0*32-vx,avatars[a].y-this.y0*32-vy);
      }
    }
    // 
    //debug('Map('+AFromLayer+'-'+AToLayer+') rendered in '+st.ms());
  }
  
}